package org.stratusdb.store.postgres;

import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;

import org.stratusdb.Config;
import org.stratusdb.store.AbstractStore;
import org.stratusdb.types.SQLInteger;
import org.stratusdb.types.SQLVarchar;
import org.stratusdb.types.TableType;
import org.stratusdb.types.TupleType;
import org.stratusdb.error.EnumError;
import org.stratusdb.error.Error;
import org.stratusdb.funsql.compile.operator.AbstractOperator;
import org.stratusdb.funsql.compile.operator.AbstractBinaryOperator;
import org.stratusdb.funsql.compile.operator.AbstractUnaryOperator;
import org.stratusdb.funsql.compile.operator.EnumOperator;
import org.stratusdb.funsql.compile.operator.EquiJoin;
import org.stratusdb.funsql.compile.operator.EquiSelection;
import org.stratusdb.funsql.compile.operator.SimpleProjection;
import org.stratusdb.funsql.compile.operator.TableOperator;
import org.stratusdb.funsql.compile.tokens.AbstractToken;
import org.stratusdb.funsql.compile.tokens.TokenAttribute;
import org.stratusdb.metadata.Catalog;
import org.stratusdb.metadata.Schema;
import org.stratusdb.metadata.Table;

public class PostgresStore extends AbstractStore {

	private java.sql.Connection con = null;
	private Error lastError = null;
	private ResultSet erg = null;
	private String myString[] = new String[3]; // [0] = Projection; [1] = Tables; [2] = Predicates
	
	@Override
	public boolean supportsOperator(EnumOperator operator) {
		switch(operator){
		case EQUI_SELECTION:
		case SIMPLE_PROJECTION:
		case EQUI_JOIN:
		case CARTESIAN_PRODUCT:
			return true;
		default:
			return false;
		}
	}

	/* deprecated
	 */
	public void sqlBuild(AbstractOperator root, boolean flag) {
		switch(root.getType()){
		  case SIMPLE_PROJECTION: 
			SimpleProjection sp = (SimpleProjection)root;
			StringBuilder proj = new StringBuilder(sp.getAttribtue(0).toString());
			for(int i=1;i<sp.getTokenAttributes().size();++i){
				proj.append(", "+sp.getAttribtue(i).toString());
			}
			myString[0] = proj.toString();
			sqlBuild(((AbstractUnaryOperator)root).getChild(),true);
			return;
		  case CARTESIAN_PRODUCT:
			  sqlBuild(((AbstractBinaryOperator) root).getLeftChild(),flag);
				sqlBuild(((AbstractBinaryOperator) root).getRightChild(),flag);
				return;
		  case EQUI_JOIN:
			  EquiJoin ej = (EquiJoin) root;
				if(myString[2]!=null)
					myString[2] = myString[2] + " AND ";
				myString[2] = ((myString[2]==null)?"":myString[2]) +ej.getLeftTokenAttribute().toSqlString()+"="+ej.getRightTokenAttribute().toSqlString();
				System.out.println(myString[2]);
				sqlBuild(((AbstractBinaryOperator) root).getLeftChild(),flag);
				sqlBuild(((AbstractBinaryOperator) root).getRightChild(),flag);
				return;
		  case TABLE_OPERATOR:
			  if(myString[1] != null) {
				  myString[1]=myString[1]+", ";
			  }
			  TableOperator to = (TableOperator) root;
			  Schema schema = Catalog.getSchema(to.getTokenTable().getSchema().hashKey());
			  Table t = Catalog.getTable(to.getTokenTable().hashKey(schema.getOid()));
			  myString[1] = ((myString[1]==null)?"":myString[1])+schema.getName()+".\""+t.getSourceName()+"\"";
			  return;
		  case EQUI_SELECTION:
			  EquiSelection es = (EquiSelection) root;
			  HashMap<TokenAttribute,AbstractToken> tmp = es.getPredicates();
			  for(TokenAttribute key : tmp.keySet()) {
				  if(myString[2]!=null) {
					  myString[2] = myString[2] + " AND ";
				  }
				  else myString[2] = "";
				  myString[2] += key.toSqlString()+ " = " + tmp.get(key).toSqlString(); 
			  }
			  sqlBuild(((AbstractUnaryOperator)root).getChild(),flag);
			  return;
		  default: return;
		}
	}

	@Override
	public String generateQuery(AbstractOperator operator) {
		switch (operator.getType()) {
		case TABLE_OPERATOR:
			TableOperator to = (TableOperator) operator;
			Schema schema = Catalog.getSchema(to.getTokenTable().getSchema().hashKey());
			Table t = Catalog.getTable(to.getTokenTable().hashKey(schema.getOid()));
			return "SELECT * FROM " + schema.getName() + ".\"" + t.getSourceName()+"\" as "+t.getName();
		case EQUI_SELECTION:
			EquiSelection es = (EquiSelection) operator;
			HashMap<TokenAttribute, AbstractToken> tmp = es.getPredicates();
			StringBuilder sb = new StringBuilder();
			for (TokenAttribute key : tmp.keySet()) {
				if (sb.length() != 0) {
					sb.append(" AND ");
				}
				sb.append(key.toSqlString() + "=" + tmp.get(key).toSqlString());
			}
			return "SELECT * FROM (" + generateQuery(es.getChild())
					+ ") as a WHERE " + sb.toString();
		case SIMPLE_PROJECTION:
			SimpleProjection sp = (SimpleProjection) operator;
			StringBuilder proj = new StringBuilder(sp.getAttribtue(0).toString());
			for (int i = 1; i < sp.getTokenAttributes().size(); ++i) {
				proj.append(", " + sp.getAttribtue(i).toString());
			}
			return "SELECT " + proj.toString() + " FROM (" + generateQuery(sp.getChild()) + ") as b";
		case CARTESIAN_PRODUCT:
			AbstractBinaryOperator abo = (AbstractBinaryOperator)operator;
			return "SELECT * FROM ("+generateQuery(abo.getLeftChild())+") as c,("+generateQuery(abo.getRightChild())+") as d";
		case EQUI_JOIN:
			  EquiJoin ej = (EquiJoin) operator;
			  return "SELECT * FROM ("+generateQuery(ej.getLeftChild())+") as e,("+generateQuery(ej.getRightChild())+") as f WHERE "+ej.getLeftTokenAttribute().toSqlString()+"="+ej.getRightTokenAttribute().toSqlString();
		default:
			  return null;
		}
		/*
		 * old
		 */
		/*
		this.sqlBuild(operator, false);
		StringBuilder query = new StringBuilder();
		query.append("SELECT ");
		if(myString[0] == null) myString[0]="*";
		query.append(myString[0]);
		query.append(" FROM ");
		query.append(myString[1]);
		if(myString[2] != null) {
			query.append(" WHERE ");
			query.append(myString[2]);
		}
		System.out.println("Query: " + query.toString());
		return query.toString();*/
	}

	@Override
	public Error openConnection(String url, String user, String passwd) {
		//if(this. != EnumStore.POSTGRES)  return;
		this.lastError = Error.NO_ERROR;
		try {
			Class.forName(Config.METADATA_DRIVER_CLASS);
			this.con = DriverManager.getConnection(url, user, passwd);
		}
		catch (Exception e) {
			this.lastError =  AbstractStore.createConnectionNotPossible(url, e.getMessage());
		}
		return this.lastError;
	}
	
	@Override
	public Error executeQuery(String query, TupleType resultPrototype) {
		//::todo:: evaluate resultPrototype here to create?
		this.lastError = Error.NO_ERROR;
		if(this.con == null) {
			String msg[] = {"Postgres.con","con not exists"};
			this.lastError = new Error(EnumError.STORE_CONNECTION_ERROR, msg);
			return this.lastError;
		}
		try {
			Statement stmt = this.con.createStatement();
			this.erg = stmt.executeQuery(query);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return this.lastError;
	}

	@Override
	public TableType fetchResult() {
		if(this.erg == null) return null;
		int amount = 0;
		try {
			amount = this.erg.getMetaData().getColumnCount();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		TableType tt = new TableType(Config.EXECUTE_MAX_FETCHSIZE);
		
		for(int i=0;i<Config.EXECUTE_MAX_FETCHSIZE;++i) {
			try {
				if(!this.erg.next()) {
					this.erg = null;
					if(i==0) return null;
					else return tt;
				}
				TupleType tuty = new TupleType(amount);
				for(int k=0;k<amount;++k) {
					int type = this.erg.getMetaData().getColumnType(k+1);
					switch(type) {
					case java.sql.Types.INTEGER:
						tuty.setValue(k, new SQLInteger(this.erg.getInt(k+1)));
					break;
					case java.sql.Types.VARCHAR:
						tuty.setValue(k, new SQLVarchar(this.erg.getString(k+1)));
					break;
					}
				}
				tt.setTuple(i, tuty);
			}
			catch (SQLException e) {
				e.printStackTrace();
			}
		}
		return tt;
	}

	@Override
	public Error executeUpdate(String update) {
		this.lastError = Error.NO_ERROR;
		if(this.con == null) {
			String msg[] = {"Postgres.con","con not exists"};
			this.lastError = new Error(EnumError.STORE_CONNECTION_ERROR, msg);
			return this.lastError;
		}
		try {
			Statement stmt = this.con.createStatement();
			stmt.executeUpdate(update);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return this.lastError;
	}

	@Override
	public Error execute(String ddl) {
		this.lastError = Error.NO_ERROR;
		if(this.con == null) {
			String msg[] = {"Postgres.con","con not exists"};
			this.lastError = new Error(EnumError.STORE_CONNECTION_ERROR, msg);
			return this.lastError;
		}
		try {
			Statement stmt = this.con.createStatement();
			stmt.execute(ddl);
		} catch (Exception e) {
			e.printStackTrace();
		}		
		return this.lastError;
	}

	@Override
	public Error closeConnection() {
		this.lastError = Error.NO_ERROR;
		try {
			this.con.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		this.con = null;
		return this.lastError;
	}

	@Override
	public void executeWithException(String ddl) throws Exception {
		// TODO Auto-generated method stub
		
	}

}
